﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using System.Windows;

namespace CPPEI.WebApi.PackageTool
{
    [Serializable]
    class AssemblyLoader : MarshalByRefObject
    {
        private static string greetings = "PONG!";

        public static ModuleInfo Load(string assemblyFile)
        {
            FileInfo fileInfo = new FileInfo(assemblyFile);

            AppDomainSetup setup = new AppDomainSetup();
            setup.LoaderOptimization = LoaderOptimization.MultiDomain;
            setup.ApplicationBase = AppDomain.CurrentDomain.BaseDirectory;
            System.Security.Policy.Evidence evidence = new System.Security.Policy.Evidence(AppDomain.CurrentDomain.Evidence);
            AppDomain newDomain = AppDomain.CreateDomain("newDomain", null, setup);
            newDomain.SetData("assemblyFile", assemblyFile);
            greetings = "PING!";
            //string name = newDomain.FriendlyName;
            newDomain.DoCallBack(new CrossAppDomainDelegate(Loading));
            ModuleInfo moduleInfo = newDomain.GetData("module") as ModuleInfo;

            ModuleInfo clonedModule = new ModuleInfo();
            Clone(moduleInfo, clonedModule);

            AppDomain.Unload(newDomain);

            Debug.WriteLine("run at "+greetings);

            ResolveRefs(clonedModule);

            return clonedModule;
        }

        private static void Loading()
        {
            Debug.WriteLine("run at " + greetings);

            string assebmlyFile = (string)AppDomain.CurrentDomain.GetData("assemblyFile");
            FileInfo assebmlyFileInfo = new FileInfo(assebmlyFile);

            byte[] data = File.ReadAllBytes(assebmlyFile);
            Assembly assembly = AppDomain.CurrentDomain.Load(data);

            ModuleInfo module = ParseModule(assembly);
            module.FilePath = assebmlyFile;
            AppDomain.CurrentDomain.SetData("module", module);
            if(!module.HasError)
            {
                AssemblyName[] references = assembly.GetReferencedAssemblies();
                foreach (AssemblyName refer in references)
                {
                    RefInfo refInfo = new RefInfo();
                    refInfo.Assembly = refer.FullName;
                    refInfo.FilePath = GetAssemblyFile(string.Format("{0}\\", assebmlyFileInfo.Directory.FullName), refer);

                    module.Refs.Add(refInfo);
                }
            }
        }

        private static ModuleInfo ParseModule(Assembly assembly)
        {
            ModuleInfo info = new ModuleInfo();
            info.Assembly = assembly.FullName;

            Type[] types = assembly.GetTypes();

            Type moduleType = null;
            List<ActionInfo> actions = new List<ActionInfo>();
            foreach (Type type in types)
            {
                if(info.HasError)
                {
                    break;
                }

                CPPEI.WebApi.ModuleAttribute moduleAttribute = type.GetCustomAttribute<CPPEI.WebApi.ModuleAttribute>();
                if(moduleAttribute != null)
                {
                    if(moduleType == null)
                    {
                        moduleType = type;
                        info.Name = moduleAttribute.Name;
                        info.Type = moduleType.FullName;
                        moduleType = type;
                    }
                    else
                    {
                        info.HasError = true;
                        info.ErrorMessage = string.Format("程序集中包含了多个模块{0}和{1}的定义", info.Name, moduleAttribute.Name);
                    }
                }

                CPPEI.WebApi.ActionAttribute actionAttribute = type.GetCustomAttribute<CPPEI.WebApi.ActionAttribute>();
                if (actionAttribute != null)
                {
                    bool isExist = false;
                    foreach (ActionInfo action in info.Actions)
                    {
                        if (string.Equals(actionAttribute.Scenario, action.Scenario) && string.Equals(actionAttribute.Name, action.Name))
                        {
                            isExist = true;
                            break;
                        }
                    }
                    
                    if(!isExist)
                    {
                        ActionInfo newAction = new ActionInfo();
                        newAction.Scenario = actionAttribute.Scenario;
                        newAction.Name = actionAttribute.Name;
                        newAction.Type = type.FullName;
                        info.Actions.Add(newAction);
                    }
                    else
                    {
                        info.HasError = true;
                        info.ErrorMessage = string.Format("重复还有scenario:{0},act:{1}的定义", actionAttribute.Scenario, actionAttribute.Name);
                    }
                }
            }

            if(moduleType == null)
            {
                info.HasError = true;
                info.ErrorMessage = "程序集中没有发现模块的定义";
            }
            return info;
        }

        private static string GetAssemblyFile(string baseDirectory, AssemblyName assemblyName)
        {
            string path = string.Format("{0}{1}.dll", baseDirectory, assemblyName.Name);
            if(File.Exists(path))
            {
                return path;
            }
            return null;
        }

        private static void Clone(ModuleInfo from, ModuleInfo to)
        {
            to.HasError = from.HasError;
            to.FilePath = from.FilePath;
            to.Assembly = from.Assembly;
            to.ErrorMessage = from.ErrorMessage;
            to.Name = from.Name;
            to.Type = from.Type;

            foreach (ActionInfo action in from.Actions)
            {
                ActionInfo clonedAction = new ActionInfo();
                clonedAction.Name = action.Name;
                clonedAction.Scenario = action.Scenario;
                clonedAction.Type = action.Type;

                to.Actions.Add(clonedAction);
            }

            foreach (RefInfo refInfo in from.Refs)
            {
                RefInfo clonedRef = new RefInfo();
                clonedRef.Assembly = refInfo.Assembly;

                clonedRef.FilePath = refInfo.FilePath;
                to.Refs.Add(clonedRef);
            }
        }

        private static void ResolveRefs(ModuleInfo module)
        {
            for (int index = 0; index < module.Refs.Count;)
            {
                bool needInclude = true;
                try
                {
                    Assembly.Load(module.Refs[index].Assembly);
                    needInclude = false;
                }
                catch
                {
                }

                if (!needInclude)
                {
                    module.Refs.RemoveAt(index);
                }
                else
                {
                    index++;
                }
            }
        }
    }
}
